Glimmer Labs Presents . . .
Script Fu for Schemers

Lab: Writing Procedures for Script-Fu

Summary: In today's laboratory, you will write new procedures for use within Script-Fu and the GIMP.

Exercise 1: Grouping Commands

As you should know from your long experience with Scheme, it is relatively easy to group a number of individual commands together into a procedure. Hence, rather than typing instructions one-at-a-time into the GIMP window, we can

  1. group the commands within a Scheme procedure;
  2. put the procedure definition in a file;
  3. load the file in the Script-Fu console; and
  4. execute the procedure.

For example, here is a simple procedure I wrote that draws the letter F and then puts a "No" sign around it.

;;; Procedure:
;;;   no-failure
;;; Parameters:
;;;   none
;;; Purpose:
;;;   Creates a new image with the universal sign for "No Fs"
;;; Produces:
;;;   Nothing.  You call this only for the side effect of
;;;   drawing a new image.
;;; Preconditions:
;;;   Must be called from within Script-Fu.
;;; Postconditions:
;;;   A new image appears on the screen.
(define no-failure
  (lambda ()
    (let* ((image (car (gimp-image-new 256 256 RGB)))
           (layer (car (gimp-layer-new image 256 256 RGB "Layer" 100 0))))
      ; Add the layer to the image
      (gimp-image-add-layer image layer 0)
      ; Clear everything
      (gimp-selection-all image)
      (gimp-edit-clear layer)
      (gimp-selection-none image)
      ; Set default background
      (gimp-palette-set-background WHITE)
      ; Draw the silly letter F
      (gimp-palette-set-foreground BLUE)
      (gimp-brushes-set-brush "Circle (11)")
      (gsfu-line layer 96 40 176 40) ; Top stroke
      (gsfu-line layer 96 40 96 216) ; Down stroke
      (gsfu-line layer 96 112 148 112) ; Middle stroke
      ; Draw the nice red circle.
      (gimp-palette-set-foreground RED)
      (gimp-brushes-set-brush "Circle (07)")
      (gimp-ellipse-select image 8 8 240 240 REPLACE 0 0 0)
      (gimp-edit-stroke layer)
      (gimp-selection-none image)
      ; Now we're ready to draw the slash.  Basic math tells us that
      ; the endpoints are offset by radius/(sqrt 2) from the center 
      ; of the circle.
      ; direction).
      (let* ((center 128)
             (radius 120)
             (offset (/ radius (sqrt 2))))
        (gsfu-line layer 
                   (- center offset) (- center offset)
                   (+ center offset) (+ center offset)))
      ; And display the image
      (gimp-display-new image))))

a. Create a new Scheme file to hold that procedure. You can use gnotepad or DrScheme (just don't expect to be able to execute the code within DrScheme). Since the procedure depends on my collection of Script-Fu utilities, you should also make sure the file loads those utilities by including the following at the top of the file.

(load "/home/rebelsky/Web/GIMP/Scripts/utils.ss")

b. Load the file into the Script-Fu console by typing (load filename). For example, if you've stored your stuff as images.ss in your public_html directory, you should use

(load "/home/username/public_html/images.ss")

c. Type (no-failure) in the Script-Fu console and see what happens.

Exercise 2: Writing Your Own Procedure

Using no-failure as a template, write and test your own procedure to draw an interesting picture. Depending on your skill level, you might draw a smiley face, a stick figure, or something else.

Exercise 3: Adding Parameters

One disadvantage of no-failure is that it always draws an image of the same size. What if we want a bigger or smaller image? One possibility is to make the image size a parameter to no-failure and then make the various numbers depend on the size. Since our image is naturally square, the width and height should probably be the same. Here's what I've come up with:

;;; Procedure:
;;;   new-no-failure
;;; Parameters:
;;;   side, an integer
;;;   color, an RGB list
;;; Purpose:
;;;   Creates a new image with the universal sign for "No Fs".
;;;   The image has width side and height side.
;;;   The F in the image has the specified color.
;;; Produces:
;;;   Nothing.  You call this only for the side effect of
;;;   drawing a new image.
;;; Preconditions:
;;;   Must be called from within Script-Fu.
;;; Postconditions:
;;;   A new image appears on the screen.
(define new-no-failure
  (lambda (side color)
    (let* ((image (car (gimp-image-new side side RGB)))
           (layer (car (gimp-layer-new image side side 
                                       RGB "Layer" 100 0)))
           (unit (/ side 32)))
      ; Add the layer to the image
      (gimp-image-add-layer image layer 0)
      ; Clear everything
      (gimp-selection-all image)
      (gimp-edit-clear layer)
      (gimp-selection-none image)
      ; Set default background
      (gimp-palette-set-background WHITE)
      ; Draw the silly letter F
      (gimp-palette-set-foreground color)
      (gimp-brushes-set-brush "Circle (11)")
      (gsfu-line layer (* unit 12) (* unit 5) 
                 (* unit 20) (* unit 5))
      (gsfu-line layer (* unit 12) (* unit 5)
                 (* unit 12) (* unit 27))
      (gsfu-line layer (* unit 12) (* unit 14)
                 (* unit 18) (* unit 14))
      ; Draw the nice red circle.
      (gimp-palette-set-foreground RED)
      (gimp-brushes-set-brush "Circle (07)")
      (gimp-ellipse-select image unit unit
                           (- side unit unit) (- side unit unit)
                           REPLACE 0 0 0)
      (gimp-edit-stroke layer)
      (gimp-selection-none image)
      ; Now we're ready to draw the slash.  Basic math tells us that
      ; the endpoints are offset by radius/(sqrt 2) from the center 
      ; of the circle.
      ; direction).
      (let* ((center (/ side 2))
             (radius (- center unit))
             (offset (/ radius (sqrt 2))))
        (gsfu-line layer 
                   (- center offset) (- center offset)
                   (+ center offset) (+ center offset)))
      ; And display the image
      (gimp-display-new image))))

a. Either add new-no-failure to your file from the first exercise or create a new file to contain it.

b. Load the file into the Script-Fu console.

c. Test the procedure by typing each of the following commands in the Script-Fu console.

(new-no-failure 256 GREEN)
(new-no-failure 100 PURPLE)

Exercise 4: Generalizing Your Procedure

Generalize your procedure from exercise 2 so that it takes the image size as a parameter. You may choose to build square images (as I did) and take only the length of a side as a parameter or you may choose to build rectangular images and take both width and height as parameters.

Exercise 5: Telling The GIMP About Procedures

With the Script-Fu that you know up to this point, all the cool Script-Fu commands you write are only available to people who are able to use the Script-Fu console. It would also be nice to add Script-Fu commands to the GIMP's various menus. Fortunately, you only need to follow a few simple steps.

1. Write Scheme code that tells the GIMP to add a procedure to the menu. Here, you need to tell the GIMP what parameters your procedure takes so that it can ask the user for those parameters.

2. Put the Scheme code in a place the GIMP can find it. Typically, you'll put your Scheme code in /home/username/.gimp-2.2/scripts. (Yes, the period before the .gimp is important.) You'll only have to do this once per script. My experience suggests that you should end the file name with .scm rather than .ss.

3. Tell the GIMP that you've added the script by selecting Refresh from the Script-Fu submenu of the Xtns menu. Close and reopen the Script-Fu console, if you have it open. (You should only have to do this when you add or modify the script in the middle of a session. In the future, the GIMP should load your script automatically.)

Steps 2 and 3 are straightforward, so let's consider the first step in more detail. You add a procedure with the script-fu-register procedure. That procedure takes a large number of parameters.

For each parameter for your procedure, you'll need three additional parameters to script-fu-register:

For SF-IMAGE and SF-DRAWABLE, the value should be 0; the GIMP will fill them in with the current image and layer. For SF-VALUE, the default value should be in quotation marks, even when it's a number. For SF-COLOR, the color should be a list of three integers (as you explored in the previous lab).

For example, here's what I might use for my no-failure procedure.

(script-fu-register
   "new-no-failure"
   "<Toolbox>/Xtns/Script-Fu/Sample/No Failure"
   "Draws a \"No Failure\" logo"
   "Samuel A. Rebelsky"
   "Copyright (c) 2001 Samuel A. Rebelsky.  All Rights Reserved"
   "Thursday, 5 April 2001"
   "RGB"
   SF-VALUE "Side Length" "256"
   SF-COLOR "Color" BLUE)

a. Make a copy of the file in your GIMP scripts directory.

b. Tell GIMP to refresh its Script-Fu list by selecting Refresh from the Script-Fu menu in the Xtns menu, and closing and reopening the Script-Fu console.

c. You should now be able to select No Failure from a new Sample submenu of the Script-Fu submenu of the Xtns menu. Try it and see what happens.

d. Register your own script from the previous exercise. See if you can get it to run.

Exercise 6: Writing Helper Procedures

When doing more complex drawings, you will often find that you draw the same thing again and again at different places and in different sizes. Hence, it is sometimes useful to write helper procedures that can do the precise drawing for you. For example, I decided that I often make the "No" sign, and wrote a helper procedure for that sign. Here's my code.

;;; Procedure:
;;;   forbidden
;;; Parameters:
;;;   image, an image
;;;   layer, a layer associated with that image
;;;   x, the x coordinate of the center of the sign
;;;   y, the y coordinate of the center of the sign
;;;   radius, the radius of the sign
;;; Purpose:
;;;   Draws a "forbidden" sign (a circle with a slash) in
;;;   red centered at the specified location.
;;; Preconditions:
;;;   image and layer are initialized.
;;;   x, y, and radius are not negative.
;;; Postconditions:
;;;   The image now contains the specified sign.
;;;   The brush and foreground color may have changed.
(define forbidden
  (lambda (image layer x y radius)
    ; Choose a color and paintbrush that seem appropriate.
    (gimp-palette-set-foreground RED)
    (gimp-brushes-set-brush "Circle (05)")
    ; Select the ellipse for the circle.  Do the math to see why.
    ; the particular values were set.
    (gimp-ellipse-select image
                         (- x radius) (- y radius)
                         (* 2 radius) (* 2 radius)
                         REPLACE
                         0 0 0)
    ; Draw the nice red circle.
    (gimp-edit-stroke layer)
    ; Now we're ready to draw the slash.  Some math tells us that
    ; it's offset by radius/(sqrt 2) from the center (in each
    ; direction).
    (let ((offset (/ radius (sqrt 2))))
      (gimp-paintbrush layer 0 4
                       (float-array (- x offset) (- y offset)
                                    (+ x offset) (+ y offset)) 0 0))
    ; Unselect all
    (gimp-selection-clear image)
    ; Flatten the image
    (let ((newlayer (car (gimp-image-flatten image))))
    ; Return the modified image/layer pair 
      (list image newlayer))
  ))

a. Once again, store that code in a file.

b. Create a new image in Script-Fu and use forbidden to draw on that image in various places and sizes.

c. Write your own helper procedure to draw a rectangle.

d. Write your own helper procedure to draw a circle.

Exercise 7: GIMP Menus, Revisited

When we write procedures like forbid, we probably want to add them to the menu for the current image, rather than the more general toolbox menu. Here's the command that I've written to add the forbid sign to the popup image menu.

(script-fu-register
   "forbidden"
   "<Image>/Script-Fu/Sample/Forbidden Sign"
   "Draws the legendary forbidden sign"
   "Samuel A. Rebelsky"
   "Copyright (c) 2001 Samuel A. Rebelsky.  All Rights Reserved"
   "Tuesday, 3 April 2001"
   "RGB"
   SF-IMAGE "Image" 0
   SF-DRAWABLE "Drawable" 0
   SF-VALUE "Center X" "100"
   SF-VALUE "Center Y" "100"
   SF-VALUE "Radius" "50")

You can find everything in forbidden.scm.

Install and test it. You may find that you have to click on the image after running the script.

History

Wednesday, 4 April 2001 [Samuel A. Rebelsky]

Thursday, 5 April 2001 [Samuel A. Rebelsky]

Thursday, 25 May 2006 [Ian Bone-Rundle]

Thursday, 1 May 2006 [Samuel A. Rebelsky]


This material is based upon work supported by the National Science Foundation under Grant No. 9850546. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.

This page was generated by Siteweaver on Thu Jun 1 08:39:06 2006.
This page may be found at http://glimmer.cs.grinnell.edu/ScriptFu/script-fu-procs.html.
You may validate this page's HTML.
The source was last modified Thu Jun 1 08:37:47 2006.